/*   This file is part of lanSimulation.
 *
 *   lanSimulation is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   lanSimulation is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with lanSimulation; if not, write to the Free Software
 *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 *   Copyright original Java version: 2004 Bart Du Bois, Serge Demeyer
 *   Copyright C++ version: 2006 Matthias Rieger, Bart Van Rompaey
 */
package lanSimulation;


import java.util.Hashtable;
import java.util.Iterator;
import lanSimulation.internals.Document;

import lanSimulation.internals.Node;
import lanSimulation.internals.Packet;
import lanSimulation.internals.Printer;
import lanSimulation.internals.Workstation;

/**
 * A <em>Network</em> represents the basic data stucture for simulating a
 * Local Area Network (LAN). The LAN network architecture is a token ring,
 * implying that packahes will be passed from one node to another, until they
 * reached their destination, or until they travelled the whole token ring.
 */
public class Network {
	/**
	 * Holds a pointer to myself. Used to verify whether I am properly
	 * initialized.
	 */
	private Network initPtr_;
	/**
	 * Holds a pointer to some "first" node in the token ring. Used to ensure
	 * that various printing operations return expected behaviour.
	 */
	private Node firstNode_;
	/**
	 * Maps the names of workstations on the actual workstations. Used to
	 * initiate the requests for the network.
	 */
	@SuppressWarnings("unchecked")
	private Hashtable workstations_;


        private LogManager logManager;

	/**
	 * Construct a <em>Network</em> suitable for holding #size Workstations.
	 * <p>
	 * <strong>Postcondition:</strong>(result.isInitialized()) & (!
	 * result.consistentNetwork());
	 * </p>
	 */
	@SuppressWarnings("unchecked")
	public Network(int size,LogManager logmanager) {
		assert size > 0;
		initPtr_ = this;
		firstNode_ = null;
		workstations_ = new Hashtable(size, 1.0f);
                logManager = logmanager;
		assert isInitialized();
		assert !consistentNetwork();
	}

	/**
	 * Return a <em>Network</em> that may serve as starting point for various
	 * experiments. Currently, the network looks as follows.
	 * 
	 * <pre>
	 * 	 Workstation Filip [Workstation] -&gt; Node -&gt; Workstation Hans [Workstation]
	 * 	 -&gt; Printer Andy [Printer] -&gt; ... 
	 * 	
	 * </pre>
	 * 
	 * <p>
	 * <strong>Postcondition:</strong>result.isInitialized() &
	 * result.consistentNetwork();
	 * </p>
	 */
	@SuppressWarnings("unchecked")
	public static Network DefaultExample(LogManager logManager) {
		Network network = new Network(2,logManager);

		Node wsFilip = new Workstation( "Filip",logManager);
		Node n1 = new Node("n1",logManager);
		Node wsHans = new Workstation("Hans",logManager);
		Node prAndy = new Printer("Andy",logManager);

		wsFilip.nextNode_ = n1;
		n1.nextNode_ = wsHans;
		wsHans.nextNode_ = prAndy;
		prAndy.nextNode_ = wsFilip;

		network.workstations_.put(wsFilip.name_, wsFilip);
		network.workstations_.put(wsHans.name_, wsHans);
		network.firstNode_ = wsFilip;

		assert network.isInitialized();
		assert network.consistentNetwork();
		return network;
	}

	/**
	 * Answer whether #receiver is properly initialized.
	 */
	public boolean isInitialized() {
		return (initPtr_ == this);
	};

	/**
	 * Answer whether #receiver contains a workstation with the given name.
	 * <p>
	 * <strong>Precondition:</strong>this.isInitialized();
	 * </p>
	 */
	public boolean hasWorkstation(String ws) {
		Node n;

		assert isInitialized();
		n = (Node) workstations_.get(ws);
		if (n == null) {
			return false;
		} else {
			return n.equalsType(Workstation.class);
		}
	};

	/**
	 * Answer whether #receiver is a consistent token ring network. A consistent
	 * token ring network - contains at least one workstation and one printer -
	 * is circular - all registered workstations are on the token ring - all
	 * workstations on the token ring are registered.
	 * <p>
	 * <strong>Precondition:</strong>this.isInitialized();
	 * </p>
	 */
	@SuppressWarnings("unchecked")
	public boolean consistentNetwork() {
		assert isInitialized();

                return !workstations_.isEmpty() &&
                            firstNode_ != null &&
                            verifyRegisteredWorkstations() &&
                            verifyWorkstationsCount() &&
                            verifyPrinterExists() &&
                            verifyTokenRing();

	}

        private boolean verifyRegisteredWorkstations(){

            Node currentNode = firstNode_;

            for (Iterator workstationIter = workstations_.values().iterator(); workstationIter
				.hasNext();) {
			currentNode = (Node) workstationIter.next();
			if (! currentNode.equalsType(Workstation.class)) {
				return false;
			}
		}

            return true;

        }

        private boolean verifyWorkstationsCount(){

            Hashtable encountered = new Hashtable(workstations_.size() * 2, 1.0f);
            int workstationsFound = 0;
            Node currentNode = firstNode_;

            while (!encountered.containsKey(currentNode.name_)) {

                    encountered.put(currentNode.name_, currentNode);

                    if (currentNode.equalsType(Workstation.class)) {
                            workstationsFound++;
                    }

                    currentNode = currentNode.nextNode_;
            }

            if(workstationsFound != workstations_.size()){
                return false;
            }

            return true;

        }


        private boolean verifyPrinterExists(){

            Hashtable encountered = new Hashtable(workstations_.size() * 2, 1.0f);
            Node currentNode = firstNode_;
            while (!encountered.containsKey(currentNode.name_)) {
                    encountered.put(currentNode.name_, currentNode);

                    if (currentNode.equalsType(Printer.class)) {
                            return true;
                    }

                    currentNode = currentNode.nextNode_;
            }
            
            return false;

        }


        private boolean verifyTokenRing(){

            Hashtable encountered = new Hashtable(workstations_.size() * 2, 1.0f);
            Node currentNode = firstNode_;
            
            while (!encountered.containsKey(currentNode.name_)) {

                    encountered.put(currentNode.name_, currentNode);
                    currentNode = currentNode.nextNode_;

            }

            if (currentNode != firstNode_){
                return false;
            }

            return true;

        }

	/**
	 * The #receiver is requested to broadcast a message to all nodes. Therefore
	 * #receiver sends a special broadcast packet across the token ring network,
	 * which should be treated by all nodes.
	 * <p>
	 * <strong>Precondition:</strong> consistentNetwork();
	 * </p>
	 * 
	 * @param report
	 *            Stream that will hold a report about what happened when
	 *            handling the request.
	 * @return Anwer #true when the broadcast operation was succesful and #false
	 *         otherwise
	 */
	public boolean requestBroadcast() {
		assert consistentNetwork();

		logManager.log("Broadcast Request\n");
		
		Packet packet = new Packet(new Document("BROADCAST"), firstNode_.name_,
				firstNode_.name_);
					
		firstNode_.receivePacket(packet);
		firstNode_.sendPacket(packet);
                logManager.flush();

                firstNode_.nextNode_.broadcastPacket(packet);
		
		logManager.log(">>> Broadcast travelled whole token ring.\n\n");
		
                return true;
	}

	/**
	 * The #receiver is requested by #workstation to print #document on
	 * #printer. Therefore #receiver sends a packet across the token ring
	 * network, until either (1) #printer is reached or (2) the packet travelled
	 * complete token ring.
	 * <p>
	 * <strong>Precondition:</strong> consistentNetwork() &
	 * hasWorkstation(workstation);
	 * </p>
	 * 
	 * @param workstation
	 *            Name of the workstation requesting the service.
	 * @param document
	 *            Contents that should be printed on the printer.
	 * @param printer
	 *            Name of the printer that should receive the document.
	 * @param report
	 *            Stream that will hold a report about what happened when
	 *            handling the request.
	 * @return Anwer #true when the print operation was succesful and #false
	 *         otherwise
	 */
	public boolean requestWorkstationPrintsDocument(String workstation,
			Document document, String printer) {
		assert consistentNetwork() & hasWorkstation(workstation);

                logManager.log("'" + workstation + "' requests printing of " +
                                            "'" + document.getText() + "' on '" + printer + "' ...\n");
		

		Packet packet = new Packet(document, workstation, printer);

		Node startNode = (Node) workstations_.get(workstation);
                startNode.sendPacket(packet);
                return startNode.nextNode_.transmitPrintPacket(packet);

		
	}

	

	/**
	 * Return a printable representation of #receiver.
	 * <p>
	 * <strong>Precondition:</strong> isInitialized();
	 * </p>
	 */
	public String toString() {
		assert isInitialized();
		StringBuffer buf = new StringBuffer(30 * workstations_.size());
		printOn(buf);
		return buf.toString();
	}

	/**
	 * Write a printable representation of #receiver on the given #buf.
	 * <p>
	 * <strong>Precondition:</strong> isInitialized();
	 * </p>
	 */
	public void printOn(StringBuffer buf) {
		assert isInitialized();
		Node currentNode = firstNode_;
		do {
			currentNode.printOn(buf);
			buf.append(" -> ");
			currentNode = currentNode.nextNode_;
		} while (currentNode != firstNode_);
		buf.append(" ... ");
	}

	/**
	 * Write a HTML representation of #receiver on the given #buf.
	 * <p>
	 * <strong>Precondition:</strong> isInitialized();
	 * </p>
	 */
	public void printHTMLOn(StringBuffer buf) {
		assert isInitialized();

		buf.append("<HTML>\n<HEAD>\n<TITLE>LAN Simulation</TITLE>\n</HEAD>\n<BODY>\n<H1>LAN SIMULATION</H1>");
		Node currentNode = firstNode_;
		buf.append("\n\n<UL>");
		do {
			buf.append("\n\t<LI> ");
			currentNode.printOn(buf);
			buf.append(" </LI>");
			currentNode = currentNode.nextNode_;
		} while (currentNode != firstNode_);
		buf.append("\n\t<LI>...</LI>\n</UL>\n\n</BODY>\n</HTML>\n");
	}

	/**
	 * Write an XML representation of #receiver on the given #buf.
	 * <p>
	 * <strong>Precondition:</strong> isInitialized();
	 * </p>
	 */
	public void printXMLOn(StringBuffer buf) {
		assert isInitialized();

		Node currentNode = firstNode_;
		buf.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<network>");
		do {
			buf.append("\n\t");
			currentNode.printXmlOn(buf);
			currentNode = currentNode.nextNode_;
		} while (currentNode != firstNode_);
		buf.append("\n</network>");
	}

}
